/*******************************************************************
 *
 * Copyright (C), 2021-2022, LS, All rights reserved.
 *
 * Author      : 
 * Create Time : 2020-12-14
 * Version     : v0.1
 * Description : 
 *******************************************************************/

/*----------------------------Head file----------------------------*/
#include "string.h"
#include "ls_ble_ota.h"
#include "ls_ble_crc16.h"
#include "ls_ble_adpter.h"
#include "ls_ble_sdk_api.h"
#include "ls_ble_sdk_config.h"
#include "ls_ble_ota_flash.h"
#include "ls_ble_ota_protocol.h"
#include "app_global_data.h"
#include "user_config.h"

#define OTA_PROTO_LOG_EN      1

#if OTA_PROTO_LOG_EN
#include "ls_ble_log.h"
#define TAG                         "ota_pro"
#define OTA_PROTO_LOG(level, ...)	LS_BLE_LOG_PRINT(level, TAG, ##__VA_ARGS__)
#else
#define OTA_PROTO_LOG(level, ...)
#endif

/*----------------------------macro file---------------------------*/
#define ota_reset	ls_ble_adpter_platform_reset

/*----------------------------type define--------------------------*/

/*----------------------------var define---------------------------*/
static ota_info_t otaInfo;
static uint32_t tmp_recv_size;
static void ota_evt_cb(ota_evt_t *p_evt);
static ota_profile_cb_t ota_cb = {
    .evt_handler = ota_evt_cb,
};

extern OtaFlashCtl_t otaFlashCtl;

/*-------------------------func declaration------------------------*/

/*-----------------------------------------------------------------*/

void enter_ota_mode(void)
{
    ls_ble_sdk_cb_evt_param_t evt_param;
    evt_param.evt = LS_BLE_SDK_CB_EVT_OTA_START;
    evt_param.len = 0;
    ls_ble_sdk_callback(&evt_param);
}


void exit_ota_mode(void)
{
    ls_ble_sdk_cb_evt_param_t evt_param;
    evt_param.evt = LS_BLE_SDK_CB_EVT_OTA_EXIT;
    evt_param.len = 0;
    ls_ble_sdk_callback(&evt_param);
}

void ota_done(void)
{
    ls_ble_sdk_cb_evt_param_t evt_param;
    evt_param.evt = LS_BLE_SDK_CB_EVT_OTA_DONE;
    evt_param.len = 0;
    ls_ble_sdk_callback(&evt_param);
}


static void ota_protocol_send_rsp(uint8_t rspOpcode, uint8_t rspValue)
{
    if(OTA_RESP_VAL_SUCCESS != rspValue) {
        OTA_PROTO_LOG(LOG_LVL_ERROR, "rspOpcode: %d, %d.", rspOpcode, rspValue);
    }
    uint8_t tx_buf[3];
    tx_buf[0] = OTA_CODE_RESPONSE;
    tx_buf[1] = rspOpcode;
    tx_buf[2] = rspValue;
    ota_notify_tx_data(tx_buf, sizeof(tx_buf));
}

static void ota_protocol_send_receipt_notification(uint32_t numOfBytes)
{
    uint8_t tx_buf[5];
    tx_buf[0] = OTA_CODE_PKT_RCPT_NOTIF;
    tx_buf[1] = BREAK_UINT32(numOfBytes, 0);
    tx_buf[2] = BREAK_UINT32(numOfBytes, 1);
    tx_buf[3] = BREAK_UINT32(numOfBytes, 2);
    tx_buf[4] = BREAK_UINT32(numOfBytes, 3);
    ota_notify_tx_data(tx_buf, sizeof(tx_buf));
}

static void ota_protocol_send_recv_size_rsp(uint32_t numOfBytes)
{
    uint8_t tx_buf[7];
    tx_buf[0] = OTA_CODE_RESPONSE;
    tx_buf[1] = OTA_CODE_IMAGE_SIZE_REQ;
    tx_buf[2] = OTA_RESP_VAL_SUCCESS;
    tx_buf[3] = BREAK_UINT32(numOfBytes, 0);
    tx_buf[4] = BREAK_UINT32(numOfBytes, 1);
    tx_buf[5] = BREAK_UINT32(numOfBytes, 2);
    tx_buf[6] = BREAK_UINT32(numOfBytes, 3);
    ota_notify_tx_data(tx_buf, sizeof(tx_buf));
}


void ota_protocol_ctrl_point_recv_handle(uint8_t *dat, uint8_t len)
{
    uint8_t opcode = dat[0];

    switch(opcode) {
        case OTA_CODE_START_OTA: {
            otaInfo.packetType = PKT_TYPE_START;
            otaInfo.imageType = dat[1];
            enter_ota_mode();
            memset(&otaFlashCtl, 0, sizeof(otaFlashCtl));
            OTA_PROTO_LOG(LOG_LVL_INFO, "Start OTA, ImageType=%d", otaInfo.imageType);
        } break;

        case OTA_CODE_RECEIVE_INIT: {
            OTA_PROTO_LOG(LOG_LVL_INFO, "OTA_CODE_RECEIVE_INIT: %x, %d", dat[1], otaInfo.initPacketLen);
            otaInfo.packetType = PKT_TYPE_INIT;
            if(dat[1] == 0x00) {		//Receive Init Packet
                otaInfo.initPacketLen = 0;
                memset(otaInfo.initPacket, 0, sizeof(otaInfo.initPacket));
            }
            else if(dat[1] == 0x01) {	//Init Packet Complete
                if(otaInfo.initPacketLen >= OTA_INIT_PAKET_SIZE) {
                    otaInfo.imageCRC16 = BUILD_UINT16(otaInfo.initPacket[otaInfo.initPacketLen-2], otaInfo.initPacket[otaInfo.initPacketLen-1]);
                    //PCB test mode unlock project number limitation.
                    if(false == g_app_data.sys.state.pcb_test) {
                        uint16_t dev_type = BUILD_UINT16(otaInfo.initPacket[0], otaInfo.initPacket[1]);
                        uint16_t project_num = BUILD_UINT16(otaInfo.initPacket[2], otaInfo.initPacket[3]);
                        OTA_PROTO_LOG(LOG_LVL_INFO, "dev_type=%d, %d", dev_type, DEVICE_TYPE);
                        OTA_PROTO_LOG(LOG_LVL_INFO, "project_num=%d, %d", project_num, PROJECT_NUM);
                        if(DEVICE_TYPE != dev_type || PROJECT_NUM != project_num) {
                            OTA_PROTO_LOG(LOG_LVL_INFO, "Error dev infon");
                            ota_protocol_send_rsp(OTA_CODE_RECEIVE_INIT, OTA_RESP_VAL_INVALID_STATE);
                            return;
                        }
                    }
                    
                    if(otaInfo.appImageSize > APP_IMG_MAX_SIZE) {
                        ota_protocol_send_rsp(OTA_CODE_RECEIVE_INIT, OTA_RESP_VAL_INVALID_STATE);
                        return;
                    }
                    
                    ota_flash_area_erase(otaInfo.appImageSize);
                    ota_protocol_send_rsp(OTA_CODE_RECEIVE_INIT, OTA_RESP_VAL_SUCCESS);
                    OTA_PROTO_LOG(LOG_LVL_INFO, "Image CRC=%04x\r\n", otaInfo.imageCRC16);
                }
                else {
                    ota_protocol_send_rsp(OTA_CODE_RECEIVE_INIT, OTA_RESP_VAL_OPER_FAILED);
                }
            }
            else {	//Invalid State
                ota_protocol_send_rsp(OTA_CODE_RECEIVE_INIT, OTA_RESP_VAL_INVALID_STATE);
            }
            OTA_PROTO_LOG(LOG_LVL_INFO, "Receive Init");
        } break;

        case OTA_CODE_RECEIVE_FW: {
            otaInfo.packetType = PKT_TYPE_FIRMWARE_DATA;
            OTA_PROTO_LOG(LOG_LVL_INFO, "Receive Fw");
        } break;

        case OTA_CODE_VALIDATE: {
            OTA_PROTO_LOG(LOG_LVL_INFO, "Ota file valid check");
            bool valid = false;
            otaInfo.packetType = PKT_TYPE_INVALID;
            
            for(uint8_t retry=0;retry<3;retry++) {
                if(true == ota_flash_check_header(otaInfo.appImageSize) && true == ota_flash_calculate_checksum(otaInfo.imageCRC16, otaInfo.appImageSize)) {
                    ota_flash_image_acitve();
                    valid = true;
                    break;
                }
            }
            
            if(true == valid) {
                ota_done();
                ota_protocol_send_rsp(OTA_CODE_VALIDATE, OTA_RESP_VAL_SUCCESS);
                OTA_PROTO_LOG(LOG_LVL_INFO, "Validate OK");
            }
            else {
                ota_protocol_send_rsp(OTA_CODE_VALIDATE, OTA_RESP_VAL_CRC_ERROR);
                exit_ota_mode();
                OTA_PROTO_LOG(LOG_LVL_ERROR, "Validate Fail");
            }
        } break;

        case OTA_CODE_ACTIVATE_N_RESET: {
            OTA_PROTO_LOG(LOG_LVL_INFO, "Activate and reset");
            //otaFlash_ActivateImage(otaInfo.imageCRC16, otaInfo.appImageSize);
            otaInfo.packetType = PKT_TYPE_INVALID;
            otaInfo.imageType = OTA_IMAGE_TYPE_UNVALID;
            otaInfo.notifyPacketsNum = NUM_OF_NTF_PKT_NUM_NOT_SET;
            otaInfo.initPacketLen = 0;
            otaInfo.blImageRecSize = 0;
            otaInfo.appImageRecSize = 0;
            otaInfo.numOfNotifyRecPackets = 0;
            ota_reset();
        } break;

        case OTA_CODE_SYS_RESET: {
            OTA_PROTO_LOG(LOG_LVL_INFO, "Sys reset");
            otaInfo.packetType = PKT_TYPE_INVALID;
            otaInfo.imageType = OTA_IMAGE_TYPE_UNVALID;
            otaInfo.notifyPacketsNum = NUM_OF_NTF_PKT_NUM_NOT_SET;
            otaInfo.initPacketLen = 0;
            otaInfo.blImageRecSize = 0;
            otaInfo.appImageRecSize = 0;
            otaInfo.numOfNotifyRecPackets = 0;
            if(DEV_MODE_OTA_DONE != g_app_data.sys.mode) {
                exit_ota_mode();
            }
        } break;

        case OTA_CODE_IMAGE_SIZE_REQ: {
            enter_ota_mode();
            otaInfo.packetType = PKT_TYPE_INVALID;
            if(otaInfo.imageType == OTA_IMAGE_TYPE_APPLICATION) {
                ota_protocol_send_recv_size_rsp(otaInfo.appImageRecSize);
            }
            else {
                ota_protocol_send_rsp(OTA_CODE_RECEIVE_INIT, OTA_RESP_VAL_INVALID_STATE);
            }
            OTA_PROTO_LOG(LOG_LVL_INFO, "Image size Req");
        } break;

        case OTA_CODE_PKT_RCPT_NOTIF_REQ: {
            //otaInfo.packetType = PKT_TYPE_INVALID;
            tmp_recv_size = 0;
            otaInfo.notifyPacketsNum = BUILD_UINT16(dat[1], dat[2]);
            otaInfo.numOfNotifyRecPackets = 0;
            OTA_PROTO_LOG(LOG_LVL_INFO, "OTA RCPT number:%d", otaInfo.notifyPacketsNum);
            if(otaInfo.notifyPacketsNum > NUM_OF_NTF_PKT_MAX_NUM) {
                ota_protocol_send_rsp(OTA_CODE_PKT_RCPT_NOTIF_REQ, OTA_RESP_VAL_DATA_SIZE);
            }
        } break;

        default:
            break;
    }
}


void ota_protocol_packet_data_recv_handle(uint8_t *dat, uint8_t len)
{
    switch(otaInfo.packetType) {
        case PKT_TYPE_START: {
            if(len == 12) {
                otaInfo.blImageSize = BUILD_UINT32(dat[4],dat[5],dat[6],dat[7]);
                otaInfo.appImageSize = BUILD_UINT32(dat[8],dat[9],dat[10],dat[11]);
                //otaInfo.imageType = OTA_IMAGE_TYPE_UNVALID;
                otaInfo.notifyPacketsNum = NUM_OF_NTF_PKT_NUM_NOT_SET;
                otaInfo.initPacketLen = 0; 
                otaInfo.blImageRecSize = 0;
                otaInfo.appImageRecSize = 0;
                tmp_recv_size = 0;
                otaInfo.numOfNotifyRecPackets = 0;
                OTA_PROTO_LOG(LOG_LVL_INFO, "BlSize=%d,AppSize=%d", otaInfo.blImageSize, otaInfo.appImageSize);
                if(otaInfo.appImageSize < APP_IMG_MAX_SIZE) {
                    ota_protocol_send_rsp(OTA_CODE_START_OTA, OTA_RESP_VAL_SUCCESS);
                }
                else {
                    ota_protocol_send_rsp(OTA_CODE_START_OTA, OTA_RESP_VAL_NOT_SUPPORTED);
                }
            }
            else {	//data length is error
                ota_protocol_send_rsp(OTA_CODE_START_OTA, OTA_RESP_VAL_NOT_SUPPORTED);
            }
        } break;

        case PKT_TYPE_INIT: {
            OTA_PROTO_LOG(LOG_LVL_INFO, "PKT_TYPE_INIT: %d", len);
            if(otaInfo.initPacketLen + len <= sizeof(otaInfo.initPacket)) {
                memcpy(otaInfo.initPacket+otaInfo.initPacketLen, dat, len);
                otaInfo.initPacketLen += len;
            }
            else {
                ota_protocol_send_rsp(OTA_CODE_RECEIVE_INIT, OTA_RESP_VAL_NOT_SUPPORTED);
            }
        } break;

        case PKT_TYPE_FIRMWARE_DATA: {
            //LS_BLE_LOG_RAW_INFO("-");
            otaInfo.numOfNotifyRecPackets++;
            if(otaInfo.imageType == OTA_IMAGE_TYPE_APPLICATION) {
                if(NUM_OF_NTF_PKT_NUM_NOT_SET == otaInfo.notifyPacketsNum) {
                    ota_flash_buf_conti_write_data(dat, len, otaInfo.appImageSize);
                    otaInfo.appImageRecSize += len;
                    //LS_BLE_LOG_RAW_INFO("recvSize: %d, %d\n", otaInfo.appImageRecSize, otaInfo.appImageSize);
                }
                else {
                    if(otaInfo.numOfNotifyRecPackets > NUM_OF_NTF_PKT_MAX_NUM) {
                        ota_protocol_send_rsp(OTA_CODE_RECEIVE_FW, OTA_RESP_VAL_OPER_FAILED);
                    }
                    memcpy(&otaFlashCtl.flash_buf[(otaInfo.numOfNotifyRecPackets-1)*20], dat, len);
                    tmp_recv_size += len;
                    if((otaInfo.appImageRecSize + tmp_recv_size) >= otaInfo.appImageSize) {
                        ota_flash_write_data(otaFlashCtl.flash_buf, tmp_recv_size);
                        otaInfo.appImageRecSize += tmp_recv_size;
                        tmp_recv_size = 0;
                        ota_protocol_send_rsp(OTA_CODE_RECEIVE_FW, OTA_RESP_VAL_SUCCESS);
                        return;
                    }
                }
                
                if(otaInfo.numOfNotifyRecPackets >= otaInfo.notifyPacketsNum) {
                    otaInfo.numOfNotifyRecPackets = 0;
                    otaInfo.appImageRecSize += tmp_recv_size;
                    ota_flash_write_data(otaFlashCtl.flash_buf, tmp_recv_size);
                    tmp_recv_size = 0;
                    ota_protocol_send_receipt_notification(otaInfo.appImageRecSize);
                }
                if(otaInfo.appImageRecSize >= otaInfo.appImageSize) {
                    OTA_PROTO_LOG(LOG_LVL_INFO, "ota receive done: %d, %d", otaInfo.appImageRecSize, otaInfo.appImageSize);
                    ota_protocol_send_rsp(OTA_CODE_RECEIVE_FW, OTA_RESP_VAL_SUCCESS);
                }
            }
            else {
                ota_protocol_send_rsp(OTA_CODE_RECEIVE_FW, OTA_RESP_VAL_NOT_SUPPORTED);
            }
        } break;

        default:
            break;
    }
}

static void ota_evt_cb(ota_evt_t *p_evt)
{
    switch(p_evt->evt_type)
    {
        case OTA_EVT_DISCONNECT: {
            exit_ota_mode();
        } break;
        
        case OTA_EVT_CP_RECEIVE_DATA: {
            ota_protocol_ctrl_point_recv_handle(p_evt->p_data, p_evt->length);
        } break;
        
        case OTA_EVT_PKT_RECEIVE_DATA: {
            ota_protocol_packet_data_recv_handle(p_evt->p_data, p_evt->length);
        } break;
        
        case OTA_EVT_CP_NOTIFICATION_ENABLED: {
            OTA_PROTO_LOG(LOG_LVL_INFO, "OTA_EVT_CP_NOTIFICATION_ENABLED");
        } break;
        
        case OTA_EVT_CP_NOTIFICATION_DISABLED: {
            OTA_PROTO_LOG(LOG_LVL_INFO, "OTA_EVT_CP_NOTIFICATION_DISABLED");
        } break;
        
        /*case OTA_EVT_NOTIFY_COMPLETE: {
            OTA_PROTO_LOG(APP_LOG_LVL_DEBUG, "OTA_EVT_NOTIFY_COMPLETE");
        } break;*/
        
        default:
            break;
    }
}


void ota_protocol_init(void)
{
    ota_service_init(&ota_cb);
}
